Skip to content

Conversation

@monkey0506
Copy link
Owner

@monkey0506 monkey0506 commented Oct 8, 2024

As originally proposed in #21, this shifts the MarshalAsAttribute and MarshalMap parameters out of the FromAction/FromFunc/FromFunctionPointer ("factory methods") method argument lists into a generic type argument of the method (not the interface).

A method call such as:

var func = INativeFunc<string>.FromFunctionPointer(ptr, CallingConvention.Cdecl, marshalReturnAs: new MarshalAsAttribute(UnmanagedType.LPUTF8Str));

Would instead become:

internal sealed class Marshaller : IMarshaller<Marshaller>
{
    static MarshalAsAttribute? IMarshaller<Marshaller>.MarshalReturnAs => new MarshalAsAttribute(UnmanagedType.LPUTF8Str);
}

// ...

var func = INativeFunc<string>.FromFunctionPointer<Marshaller>(ptr, CallingConvention);

IMarshaller<TSelf>

This introduces the new interface IMarshaller<TSelf>:

internal interface IMarshaller<TSelf> where TSelf : IMarshaller<TSelf>
{
    protected static virtual CallingConvention? CallingConvention => null;
    protected static virtual MarshalMap? MarshalMap => null;
    protected static virtual MarshalAsAttribute?[]? MarshalParamsAs => null;
    protected static virtual MarshalAsAttribute? MarshalReturnAs => null;
}

Each of the factory methods now have both generic and non-generic overloads. The non-generic overloads will not provide any custom runtime marshalling behaviors. The generic methods take a single generic type parameter, TMarshaller, which is constrained by where TMarshaller : IMarshaller<TMarshaller>, new(). Types implementing IMarshaller<TSelf> are not intended to be instanciated at runtime at all. This is a means to supply the custom marshallling behaviors at the call-site of the factory methods, which cannot be achieved through, for example, an attribute.

Diagnostics

This PR removes all diagnostics that were previously generated, but the restrictions on the MarshalAsAttribute and MarshalMap parameters are still in effect. I am undecided if the diagnostics should be added back in, or if a failure to parse the marshalling should be silently ignored.

I am weighing the option of writing an analyzer to look for these issues specifically, as the analyzer can exist alongside the generator in the same assembly. This PR will not hinge on that decision.

CallingConvention

The factory methods still accept a CallingConvention parameter, which creates potential conflict between the custom marshaller definition and the factory method argument. This is resolved in a similar fashion to a conflict between the MarshalMap and the MarshalAsAttribute properties - the method argument is given precedence over the marshaller property.

If the method argument can be statically parsed, then code generation is optimized (as previously implemented); otherwise if the method argument is explicitly specified (e.g., it is not defaulted), then its value will be parsed at runtime when the factory method is called.

If the method argument is omitted (defaulted), then a non-null value from the marshaller property will be used for optimal code generation.

If the method argument is omitted (defaulted) for the non-generic methods (with no custom marshaller), the marshaller does not supply the CallingConvention property, or the property is null, then the final fallback will be to pass CallingConvention.Winapi as a runtime argument.

Bug fixes

Because this PR touched so many files and lines of code, it may inadvertently have included bug fixes encountered along the way. It's equally possible that some of them were created along the way. Stopping work on this feature to try and create separate commits for each of these (including testing that they existed in the existing branch) was not a high priority.

@monkey0506 monkey0506 added the enhancement New feature or request label Oct 8, 2024
@monkey0506 monkey0506 added this to the v2.0.0 milestone Oct 8, 2024
@monkey0506 monkey0506 self-assigned this Oct 8, 2024
monkey0506 referenced this pull request Oct 8, 2024
Currently the interface methods do not declare any generic methods, yet there
remains explicit handling for these cases. A future version may support
generic methods for unmanaged function pointers (requiring  to be
enabled), but those methods can defer to the non-generic
 method directly. No other generic methods are foreseen
to be added to the interfaces at this time.
@monkey0506
Copy link
Owner Author

@monkey0506 monkey0506 merged commit edeea18 into interceptors Oct 10, 2024
@monkey0506 monkey0506 deleted the generic-marshalling branch October 10, 2024 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants